package com.smallrig.mall.template.auto;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.apache.shardingsphere.sharding.algorithm.config.AlgorithmProvidedShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.algorithm.sharding.classbased.ClassBasedShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.spi.ShardingAlgorithm;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;

@Component
@Slf4j
public class AutoCreateTableConfig implements ApplicationRunner {

	@Resource
	private ShardingSphereDataSource shardingSphereDataSource;

	@Override
	public void run(ApplicationArguments args) throws Exception {
		refreshShardingNodes();
	}

	//1 0 0 L * ? * 每个月最后一天 00:00:01
	//1 0 0 1 * ?  每个月第一天 00:00:01
	/**
	 * 物理表更新逻辑，可使用定时器每天更新物理表
	 */
	@Scheduled(cron = "1 0 0 1 * ?")
	private void refreshShardingNodes() throws Exception {
		log.info("refreshShardingNodes，自动刷新sharding配置，自动建表");
		// 获取context信息
		ContextManager contextManager = shardingSphereDataSource.getContextManager();

		Collection<RuleConfiguration> newRuleConfigs = new LinkedList<>();

		// 获取配置的分片信息
		Collection<RuleConfiguration> configurations = contextManager
				.getMetaDataContexts()
				.getMetaDataMap()
				.get("logic_db")
				.getRuleMetaData()
				.getConfigurations();

		boolean change = false;
		for (RuleConfiguration configuration : configurations) {
			//先不考虑影子表，加解密等功能
			if(!(configuration instanceof AlgorithmProvidedShardingRuleConfiguration)){
				newRuleConfigs.add(configuration);
				continue;
			}
			// 处理分片信息
			AlgorithmProvidedShardingRuleConfiguration oldConfig = (AlgorithmProvidedShardingRuleConfiguration) configuration;

			Map<String, ShardingAlgorithm> shardingAlgorithms = oldConfig.getShardingAlgorithms();

			AlgorithmProvidedShardingRuleConfiguration newConfig = new AlgorithmProvidedShardingRuleConfiguration();

			Collection<ShardingTableRuleConfiguration> newConfigs = new LinkedList<>();

			// 获取node列表配置
			for (ShardingTableRuleConfiguration shardingTableRuleConfiguration : oldConfig.getTables()) {
				// 处理node信息，建表，自动刷新node都在此方法处理
				ImmutablePair<ShardingTableRuleConfiguration,Boolean> pair = createTable(shardingTableRuleConfiguration, shardingAlgorithms);
				newConfigs.add(pair.left);
				change |= pair.right;
			}

			newConfig.setTables(newConfigs);
			BeanUtils.copyProperties(oldConfig,newConfig,"tables");

			newRuleConfigs.add(newConfig);
		}

		if(change){
			contextManager.alterRuleConfiguration("logic_db", newRuleConfigs);
			// 将新数据添加进contex中
			modifyField("contextManager",shardingSphereDataSource,contextManager);
			log.info("refreshShardingNodes,配置更新成功");
		}else{
			log.info("refreshShardingNodes,配置没有发生改动，无需更新");
		}
	}



	private ImmutablePair<ShardingTableRuleConfiguration,Boolean> createTable(
			ShardingTableRuleConfiguration ruleCfg,
			Map<String, ShardingAlgorithm> shardingAlgorithms) throws Exception {

		//这里这负责创建表，不负责创建库
		ShardingAlgorithm shardingAlgorithm = shardingAlgorithms.get(ruleCfg.getTableShardingStrategy().getShardingAlgorithmName());

		String actualDataNodes = null;
		// 通过反射执行‘分表自定义类’的构建物理表方法
		String logicTable = ruleCfg.getLogicTable();
		String oldActualDataNodes = ruleCfg.getActualDataNodes();
		AutoCreateTable myAlgorithm = null;
		if(shardingAlgorithm instanceof ClassBasedShardingAlgorithm){
			// 获取当前分表使用的自定义类的全路径
			String algorithmClassName = shardingAlgorithm.getProps().getProperty("algorithmClassName");
			if (StringUtils.isBlank(algorithmClassName)) {
				return ImmutablePair.of(ruleCfg,false);
			}
			// 反射拿到自定分表类，node节点信息，建表都在此类中处理，反射调用
			Class<?> aClass = Class.forName(algorithmClassName);

			Object o = aClass.newInstance();
			if(o instanceof AutoCreateTable){
				myAlgorithm = (AutoCreateTable) o;
			}
		}else if(shardingAlgorithm instanceof AutoCreateTable){
			myAlgorithm = (AutoCreateTable) shardingAlgorithm;
		}

		if(null!=myAlgorithm){
			actualDataNodes = myAlgorithm.buildNodes(oldActualDataNodes);
			myAlgorithm.createTables(shardingSphereDataSource, logicTable);
		}


		if (StringUtils.isBlank(actualDataNodes) || actualDataNodes.equals(oldActualDataNodes)) {
			return ImmutablePair.of(ruleCfg,false);
		}


		ShardingTableRuleConfiguration newRuleConfig = new ShardingTableRuleConfiguration(logicTable, actualDataNodes);

		BeanUtils.copyProperties(ruleCfg,newRuleConfig,"actualDataNodes");
		return ImmutablePair.of(newRuleConfig,true);
	}



	private Object getField(Object obj,String fieldName) throws NoSuchFieldException, IllegalAccessException {
		Field field = obj.getClass().getDeclaredField(fieldName);
		field.setAccessible(true);
		return field.get(obj);
	}

	private void modifyField(String fieldName,Object obj,Object param) throws NoSuchFieldException, IllegalAccessException {
		Field actualDataNodesField = obj.getClass().getDeclaredField(fieldName);
		Field modifiersField = Field.class.getDeclaredField("modifiers");
		modifiersField.setAccessible(true);
		modifiersField.setInt(actualDataNodesField, actualDataNodesField.getModifiers() & ~Modifier.FINAL);
		actualDataNodesField.setAccessible(true);
		actualDataNodesField.set(obj, param);
	}

}
